﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Application.Services;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Xms.Abp.Application.Dtos;
using Xms.Abp.Identity;

namespace Xms.Abp.Application.Services;

public static class ReadOnlyAppServiceExtensions
{
    public static Task FullingResultAuditingAsync<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput>(
        this AbstractKeyReadOnlyAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput> service,
        TGetOutputDto result,
        bool isCreatorShowName = true,
        bool isLastModifierShowName = true,
        bool isReviewerShowName = true,
        bool isDeleterShowName = true,
        bool isMaskName = false)
        where TEntity : class, IEntity
    {
        return FullingResultAuditingAsync(service, new[] { result }, isCreatorShowName, isLastModifierShowName, isReviewerShowName, isDeleterShowName, isMaskName);
    }

    public static Task FullingResultAuditingAsync<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput>(
        this AbstractKeyReadOnlyAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput> service,
        IEnumerable<TGetListOutputDto> results,
        bool isCreatorShowName = true,
        bool isLastModifierShowName = true,
        bool isReviewerShowName = true,
        bool isDeleterShowName = true,
        bool isMaskName = false)
        where TEntity : class, IEntity
    {
        return FullingResultAuditingAsync<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TGetListOutputDto>(service, results, isCreatorShowName, isLastModifierShowName, isReviewerShowName, isDeleterShowName, isMaskName);
    }

    private static string MaskName(string name, bool isMask)
    {
        if (!isMask) return name;
        if (name.Length < 2) return name;
        if (name.Length == 2) return name[0] + "*";
        return name[0] + "".PadLeft(name.Length - 2, '*') + name.Last();
    }

    public static async Task FullingResultAuditingAsync<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TResult>(
        this AbstractKeyReadOnlyAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput> service,
        IEnumerable<TResult> results,
        bool isCreatorShowName = true,
        bool isLastModifierShowName = true,
        bool isReviewerShowName = true,
        bool isDeleterShowName = true,
        bool isMaskName = false,
        params (Func<TResult, Guid>, Action<TResult, string>)[] keyNameSelectors)
        where TEntity : class, IEntity
    {
        if (results == null || !results.Any()) return;
        var userManage = service.LazyServiceProvider.LazyGetRequiredService<IUserManager>();
        var creatorIds = results.OfType<ICreationAuditedObject<string>>().Where(r => r.CreatorId.HasValue).Select(r => r.CreatorId.Value);
        var lastModifierIds = results.OfType<IModificationAuditedObject<string>>().Where(r => r.LastModifierId.HasValue).Select(r => r.LastModifierId.Value);
        var reviewerIds = results.OfType<IReviewAuditObject<string>>().Where(r => r.ReviewerId.HasValue).Select(r => r.ReviewerId.Value);
        var deleterIds = results.OfType<IDeletionAuditedObject<string>>().Where(r => r.DeleterId.HasValue).Select(r => r.DeleterId.Value);
        var userIds = creatorIds.Union(lastModifierIds).Union(reviewerIds).Union(deleterIds);
        if (keyNameSelectors != null && keyNameSelectors.Length > 0)
        {
            foreach (var (keySelector, nameSetter) in keyNameSelectors)
            {
                userIds = userIds.Union(results.Select(keySelector));
            }
        }
        userIds = userIds.Distinct();
        if (userIds == null || !userIds.Any()) return;
        var users = await userManage.GetListByIdAsync(userIds);
        var dict = users.ToDictionary(r => r.Id, r => r);
        foreach (var result in results)
        {
            if (result is ICreationAuditedObject<string> creationDto && creationDto.CreatorId.HasValue && dict.ContainsKey(creationDto.CreatorId.Value))
            {
                var creator = dict[creationDto.CreatorId.Value];
                ObjectHelper.TrySetProperty(creationDto, e => e.Creator, () => isCreatorShowName ? MaskName(creator.Name, isMaskName) : creator.NickName);
            }

            if (result is IModificationAuditedObject<string> lastModificationDto && lastModificationDto.LastModifierId.HasValue && dict.ContainsKey(lastModificationDto.LastModifierId.Value))
            {
                var lastModifier = dict[lastModificationDto.LastModifierId.Value];
                lastModificationDto.LastModifier = isLastModifierShowName ? MaskName(lastModifier.Name, isMaskName) : lastModifier.NickName;
            }

            if (result is IReviewAuditObject<string> reviewDto && reviewDto.ReviewerId.HasValue && dict.ContainsKey(reviewDto.ReviewerId.Value))
            {
                var reviewer = dict[reviewDto.ReviewerId.Value];
                reviewDto.Reviewer = isReviewerShowName ? MaskName(reviewer.Name, isMaskName) : reviewer.NickName;
            }

            if (result is IDeletionAuditedObject<string> deletionDto && deletionDto.DeleterId.HasValue && dict.ContainsKey(deletionDto.DeleterId.Value))
            {
                var deleter = dict[deletionDto.DeleterId.Value];
                deletionDto.Deleter = isDeleterShowName ? MaskName(deleter.Name, isMaskName) : deleter.NickName;
            }

            if (keyNameSelectors != null && keyNameSelectors.Length > 0)
            {
                foreach (var (keySelector, nameSetter) in keyNameSelectors)
                {
                    var userId = keySelector(result);
                    if (dict.ContainsKey(userId))
                    {
                        var user = dict[userId];
                        nameSetter(result, MaskName(user.Name, isMaskName));
                    }
                }
            }
        }

    }
}

